package com.demo.chapter6collect;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Stream;

import com.demo.domain.Dish;

import static java.util.stream.Collectors.*;

public class GroupDemo {

	enum CaloricLevel { DIET, NORMAL, FAT };

	public static void demo(){
		//按菜肴的类型进行分组
		//groupingBy需要一个Function，我们称为分类函数
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType)));

		//分类函数可以接受lambda表达式
		Map<Object, List<Dish>> res = Dish.menu.stream().collect(groupingBy(dish -> {
			if (dish.getCalories() <= 400) return CaloricLevel.DIET;
            else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
		}));
		System.out.println(res);
	}

	public static void demo2(){
		//多级分组
		//除了接受一个分类函数，还可以接受collector类型的第二个参数
		Map<Dish.Type, Map<CaloricLevel, List<Dish>>> res = Dish.menu.stream().collect(
			groupingBy(Dish::getType,
				groupingBy(dish -> {
					if (((Dish)dish).getCalories() <= 400) return CaloricLevel.DIET;
		            else if (((Dish)dish).getCalories() <= 700) return CaloricLevel.NORMAL;
		            else return CaloricLevel.FAT;
				})
			)
		);
		System.out.println(res);
	}

	public static void demo3(){
		//按子组收集数据
		//第二个参数不一定非要是groupingBy，也可以是counting等，返回组里元素个数
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType, counting())));
		//按类型分组，返回类型里最高卡路里的菜
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType, maxBy(Comparator.comparing(Dish::getCalories)))));
	}

	public static void demo4(){
		//把收集器的结果转换为另一种类型，例如不希望返回Optional，可以用collectingAndThen，
		//这个方法接受2个参数：1、要转换的收集器，2、转换函数，返回另一个收集器
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType, collectingAndThen(
				maxBy(Comparator.comparing(Dish::getCalories)), Optional::get))));
	}

	public static void demo5(){
		//groupingBy联合使用mapping
		//mapping接受2个参数：1、一个函数对流中的元素做变换，2、把变换的结果对象收集起来
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> {
			if (((Dish)dish).getCalories() <= 400) return CaloricLevel.DIET;
            else if (((Dish)dish).getCalories() <= 700) return CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
		}, toSet()))));
		//toSet()返回的set是什么类型没有保障，可以使用toCollection，给它传递一个构造函数
		System.out.println(Dish.menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> {
			if (((Dish)dish).getCalories() <= 400) return CaloricLevel.DIET;
            else if (((Dish)dish).getCalories() <= 700) return CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
		}, toCollection(HashSet::new)))));
	}

	/**根据明细详情生成明细汇总
	 * @param details 明细详情
	 * @return 明细汇总list
	 */
	public static List<InvoiceTotaldetail> createTotalDetail(List<InvoiceDetail> details){
		Function<InvoiceDetail, InvoiceTotaldetail> mapper = detail -> {
			InvoiceTotaldetail tdetail = new InvoiceTotaldetail();
			tdetail.setInvoiceId(detail.getInvoiceId());
			tdetail.setInvoiceClassId(detail.getInvoiceClassId());
			tdetail.setProductName(detail.getInvoiceDetailName());
			tdetail.setProductNum(1);
			tdetail.setTaxmoney(detail.getProductTaxmoney());
			return tdetail;
		};
		BinaryOperator<InvoiceTotaldetail> combiner = (left, right) -> {
			right.setTaxmoney(left.getTaxmoney().add(right.getTaxmoney()));
			return right;
		};
		//汇总明细详情
		InvoiceTotaldetail[] tdArr = details
				.stream()
				.collect(groupingBy(InvoiceDetail::getInvoiceClassId, reducing(new InvoiceTotaldetail(new BigDecimal(0)), mapper, combiner)))
				.values()
				.stream()
				.toArray(InvoiceTotaldetail[]::new);

		//如果有邮寄费
		Optional<InvoiceTotaldetail> sendFee = Stream.of(tdArr).filter(td -> td.getInvoiceClassId().equals(InvoiceDetailClassEnum.send_fee)).findFirst();
		if(sendFee.isPresent()){
			//有图书费，则邮寄费归到图书费
			Optional<InvoiceTotaldetail> bookFee = Stream.of(tdArr).filter(td -> td.getInvoiceClassId().equals(InvoiceDetailClassEnum.book_fee)).findFirst();
			if(bookFee.isPresent()){
				bookFee.get().setTaxmoney(bookFee.get().getTaxmoney().add(sendFee.get().getTaxmoney()));
			}else{
				//否则，有培训费，则邮寄费归到培训费
				Optional<InvoiceTotaldetail> trainFee = Stream.of(tdArr).filter(td -> td.getInvoiceClassId().equals(InvoiceDetailClassEnum.train_fee)).findFirst();
				if(trainFee.isPresent()){
					trainFee.get().setTaxmoney(trainFee.get().getTaxmoney().add(sendFee.get().getTaxmoney()));
				}else{
					//否则，有服务费，则邮寄费归到服务费
					Optional<InvoiceTotaldetail> serviceFee = Stream.of(tdArr).filter(td -> td.getInvoiceClassId().equals(InvoiceDetailClassEnum.service_fee)).findFirst();
					if(serviceFee.isPresent()){
						serviceFee.get().setTaxmoney(serviceFee.get().getTaxmoney().add(sendFee.get().getTaxmoney()));
					}else{
						//否则，直接变为服务费
						sendFee.get().setInvoiceClassId(InvoiceDetailClassEnum.service_fee);
					}
				}
			}
		}

		//最后删除邮寄费
		return Stream.of(tdArr).filter(td -> !td.getInvoiceClassId().equals(InvoiceDetailClassEnum.send_fee)).collect(toList());
	}

	public static void main(String[] args) {
		demo();
		demo2();
		demo3();
		demo4();
		demo5();

		List<InvoiceDetail> details = new ArrayList<>();
		InvoiceDetail d = new InvoiceDetail();
		d.setInvoiceDetailId(1l);
		d.setInvoiceId(1000l);
		d.setInvoiceClassId(InvoiceDetailClassEnum.book_fee);
		d.setInvoiceDetailName("图书费");
		d.setProductTaxmoney(new BigDecimal(100));
		details.add(d);

		InvoiceDetail d2 = new InvoiceDetail();
		d2.setInvoiceDetailId(2l);
		d2.setInvoiceId(1000l);
		d2.setInvoiceClassId(InvoiceDetailClassEnum.book_fee);
		d2.setInvoiceDetailName("图书费");
		d2.setProductTaxmoney(new BigDecimal(200));
		details.add(d2);

		InvoiceDetail d3 = new InvoiceDetail();
		d3.setInvoiceDetailId(3l);
		d3.setInvoiceId(1000l);
		d3.setInvoiceClassId(InvoiceDetailClassEnum.train_fee);
		d3.setInvoiceDetailName("培训费");
		d3.setProductTaxmoney(new BigDecimal(200));
		details.add(d3);

		InvoiceDetail d4 = new InvoiceDetail();
		d4.setInvoiceDetailId(3l);
		d4.setInvoiceId(1000l);
		d4.setInvoiceClassId(InvoiceDetailClassEnum.send_fee);
		d4.setInvoiceDetailName("邮寄费");
		d4.setProductTaxmoney(new BigDecimal(6));
		details.add(d4);

		InvoiceDetail d5 = new InvoiceDetail();
		d5.setInvoiceDetailId(3l);
		d5.setInvoiceId(1000l);
		d5.setInvoiceClassId(InvoiceDetailClassEnum.train_fee);
		d5.setInvoiceDetailName("培训费");
		d5.setProductTaxmoney(new BigDecimal(600));
		details.add(d5);

		createTotalDetail(details);
	}

}
